package com.tyk.bookstore.back.logistics.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tyk.bookstore.back.common.core.model.exception.CustomException;
import com.tyk.bookstore.back.common.core.model.result.CommonResultEnum;
import com.tyk.bookstore.back.common.core.model.type.BinVo;
import com.tyk.bookstore.back.common.core.util.AssertUtil;
import com.tyk.bookstore.back.common.dal.mapper.dto.SinDto;
import com.tyk.bookstore.back.logistics.manager.excel.model.WaybillExcelVo;
import com.tyk.bookstore.back.logistics.model.entity.ExpressCompanyDo;
import com.tyk.bookstore.back.logistics.model.entity.WaybillDo;
import com.tyk.bookstore.back.logistics.model.entity.WaybillTimelineDo;
import com.tyk.bookstore.back.logistics.model.enume.WaybillState;
import com.tyk.bookstore.back.logistics.model.query.WaybillPageQuery;
import com.tyk.bookstore.back.logistics.model.query.WaybillUpdateQuery;
import com.tyk.bookstore.back.logistics.model.result.ExpressCompanyResultEnum;
import com.tyk.bookstore.back.logistics.model.result.WaybillResultEnum;
import com.tyk.bookstore.back.logistics.model.vo.WaybillPageVo;
import com.tyk.bookstore.back.logistics.model.vo.WaybillVo;
import com.tyk.bookstore.back.logistics.repository.ExpressCompanyRepository;
import com.tyk.bookstore.back.logistics.repository.WaybillRepository;
import com.tyk.bookstore.back.logistics.repository.WaybillTimelineRepository;
import com.tyk.bookstore.back.logistics.service.WaybillService;
import com.tyk.bookstore.back.logistics.util.MapStructLogistics;
import com.tyk.bookstore.back.platform.service.AreaApiService;
import com.tyk.bookstore.back.storage.model.dto.WarehouseNameDto;
import com.tyk.bookstore.back.storage.service.WarehouseApiService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author tyk
 * @description 运单管理
 */
@Service
@RequiredArgsConstructor
public class WaybillServiceImp extends ServiceImpl<WaybillRepository, WaybillDo> implements WaybillService {
    private final WaybillRepository waybillRepository;
    private final WaybillTimelineRepository waybillTimelineRepository;
    private final ExpressCompanyRepository expressCompanyRepository;
    private final AreaApiService areaApiService;
    private final WarehouseApiService warehouseApiService;
    private final MapStructLogistics mapStructLogistics;
    private final TransactionTemplate transactionTemplate;

    @Override
    public WaybillVo getById(Long id) {

        // 运单存在
        WaybillDo wd = waybillRepository.selectById(id);
        AssertUtil.notNull(wd, WaybillResultEnum.WAYBILL_NOT_FOUND);
        WaybillVo wv = mapStructLogistics.toWaybillVo(wd);

        // 仓库名
        wv.setWarehouseName(
                warehouseApiService.getNameById(wd.getWarehouseId())
        );

        // 快递公司名
        SinDto<String> expDto = expressCompanyRepository.selectSin1Eq(
                ExpressCompanyDo::getBriefName,
                ExpressCompanyDo::getId, wd.getExpressCompanyId(),
                ExpressCompanyDo.class
        );
        AssertUtil.notNull(expDto, ExpressCompanyResultEnum.EXPRESS_COMPANY_NOT_FOUND);

        // 区域名
        Map<Long, String> anMap = areaApiService
                .getFullNameListByCodeSet(
                        Set.of(wd.getCneeAreaCode(), wd.getSenderAreaCode())
                )
                .stream().collect(Collectors.toMap(BinVo::getId, BinVo::getValue));

        // 补充
        wv.setExpressCompanyName(expDto.getV1())
                .setSenderAreaFullName(
                        anMap.get(wv.getSenderAreaCode())
                )
                .setCneeAreaFullName(
                        anMap.get(wv.getCneeAreaCode())
                );

        return wv;
    }

    @Override
    public WaybillPageVo getPage(WaybillPageQuery query) {

        // 查询
        Page<WaybillDo> page = new Page<>(query.getPage(), query.getLimit());
        waybillRepository.selectPage(page, buildPageQueryWrapper(query));
        WaybillPageVo wpv = mapStructLogistics.toWaybillPageVo(page);

        // 仓库信息
        Map<Long, String> wnMap = warehouseApiService
                .getWarehouseNameDtoListByIdSet(
                        wpv.getRecords().stream().map(WaybillPageVo.Waybill::getWarehouseId).collect(Collectors.toSet())
                )
                .stream().collect(Collectors.toMap(WarehouseNameDto::getId, WarehouseNameDto::getName));

        // 快递公司信息
        Map<Long, String> enMap = expressCompanyRepository
                .<BigInteger, String, Long>selectBinList1In(
                        ExpressCompanyDo::getId, ExpressCompanyDo::getBriefName,
                        ExpressCompanyDo::getId, wpv.getRecords().stream().map(WaybillPageVo.Waybill::getExpressCompanyId).collect(Collectors.toSet()),
                        ExpressCompanyDo.class
                )
                .stream().collect(Collectors.toMap(
                        it -> it.getV1().longValue(),
                        com.tyk.bookstore.back.common.dal.mapper.dto.BinDto::getV2
                ));

        // 区域信息
        HashSet<Long> codeSet = new HashSet<>();
        codeSet.addAll(wpv.getRecords().stream().map(WaybillPageVo.Waybill::getSenderAreaCode).collect(Collectors.toSet()));
        codeSet.addAll(wpv.getRecords().stream().map(WaybillPageVo.Waybill::getCneeAreaCode).collect(Collectors.toSet()));
        codeSet.remove(null);
        Map<Long, String> anMap = areaApiService
                .getFullNameListByCodeSet(codeSet)
                .stream().collect(Collectors.toMap(BinVo::getId, BinVo::getValue));

        // 补充仓库信息和快递公司信息
        wpv.getRecords().forEach(it -> {
            it.setWarehouseName(
                    wnMap.get(it.getWarehouseId())
            ).setExpressCompanyName(
                    enMap.get(it.getExpressCompanyId())
            ).setSenderAreaFullName(
                    anMap.get(it.getSenderAreaCode())
            ).setCneeAreaFullName(
                    anMap.get(it.getCneeAreaCode())
            );
        });

        return wpv;
    }

    @Override
    public void updateById(WaybillUpdateQuery query) {

        // 查询运单当前状态
        SinDto<Integer> dto = waybillRepository.selectSin1Eq(
                WaybillDo::getState,
                WaybillDo::getId, query.getId(),
                WaybillDo.class
        );
        AssertUtil.notNull(dto, WaybillResultEnum.WAYBILL_NOT_FOUND);
        AssertUtil.equal(dto.getV1() + 1, query.getState().value(), WaybillResultEnum.STATE_NOT_SUPPORT);

        // 更新运单状态
        waybillRepository.incr1Eq(
                WaybillDo::getState, 1,
                WaybillDo::getId, query.getId(),
                WaybillDo.class
        );

        // 更新运单时间轴
        SFunction<WaybillTimelineDo, LocalDateTime> timeField = switch (query.getState()) {
            case WAREHOUSE_RECEIVED -> WaybillTimelineDo::getWarehouseReceivedTime;
            case DELIVERED -> WaybillTimelineDo::getDeliveredTime;
            case PICKED -> WaybillTimelineDo::getPickedTime;
            case TRANSPORTING -> WaybillTimelineDo::getTransportingTime;
            case DISPATCHING -> WaybillTimelineDo::getDispatchingTime;
            case WAIT_PICK -> WaybillTimelineDo::getWaitPackTime;
            case SIGNED -> WaybillTimelineDo::getSignedTime;
            default -> throw new CustomException(CommonResultEnum.SYSTEM_ERROR);
        };
        waybillTimelineRepository.update1Set1Eq(
                timeField, LocalDateTime.now(),
                WaybillTimelineDo::getWaybillId, query.getId(),
                WaybillTimelineDo.class
        );
    }

    @Override
    public void deleteById(Long id) {
        AssertUtil.isTrue(
                waybillRepository.exist1Eq(
                        WaybillDo::getId, id, WaybillDo.class
                ),
                WaybillResultEnum.WAYBILL_NOT_FOUND
        );
        waybillTimelineRepository.deleteById(id);
        waybillRepository.deleteById(id);
    }

    @Override
    public void deleteBatchIdSet(Set<Long> idSet) {
        AssertUtil.equal(
                waybillRepository.count1In(
                        WaybillDo::getId, idSet, WaybillDo.class
                ),
                (long) idSet.size(),
                WaybillResultEnum.WAYBILL_NOT_FOUND
        );
        waybillTimelineRepository.deleteBatchIds(idSet);
        waybillRepository.deleteBatchIds(idSet);
    }

    @Override
    public void changeState(Long id, WaybillState ns) {

        // 运单存在
        AssertUtil.isTrue(
                waybillRepository.exist1Eq(
                        WaybillDo::getId, id, WaybillDo.class
                ),
                WaybillResultEnum.WAYBILL_NOT_FOUND
        );

        // 仅允许状态按既定顺序变化
        SinDto<WaybillState> dto = waybillRepository.selectSin1Eq(
                WaybillDo::getState,
                WaybillDo::getId, id,
                WaybillDo.class
        );
        WaybillState os = dto.getV1();
        AssertUtil.equal(os.value() + 1, ns.value(), WaybillResultEnum.STATE_NOT_SUPPORT);

        // 更新
        waybillRepository.update1Set2Eq(
                WaybillDo::getState, ns,
                WaybillDo::getId, id,
                WaybillDo::getState, os,
                WaybillDo.class
        );

    }

    @Override
    public List<WaybillExcelVo> getWaybillExcelVoList(WaybillPageQuery query) {
        // 查询
        LambdaQueryWrapper<WaybillDo> w = buildPageQueryWrapper(query);
        List<WaybillDo> doList = waybillRepository.selectList(w);
        List<WaybillExcelVo> voList = mapStructLogistics.toWaybillExcelVoList(doList);

        // 仓库信息
        Map<Long, String> wnMap = warehouseApiService
                .getWarehouseNameDtoListByIdSet(
                        doList.stream().map(WaybillDo::getWarehouseId).collect(Collectors.toSet())
                )
                .stream().collect(Collectors.toMap(WarehouseNameDto::getId, WarehouseNameDto::getName));

        // 快递公司信息
        Map<Long, String> enMap = expressCompanyRepository
                .<BigInteger, String, Long>selectBinList1In(
                        ExpressCompanyDo::getId, ExpressCompanyDo::getBriefName,
                        ExpressCompanyDo::getId, doList.stream().map(WaybillDo::getExpressCompanyId).collect(Collectors.toSet()),
                        ExpressCompanyDo.class
                )
                .stream().collect(Collectors.toMap(
                        it -> it.getV1().longValue(),
                        com.tyk.bookstore.back.common.dal.mapper.dto.BinDto::getV2
                ));

        // 区域信息
        HashSet<Long> codeSet = new HashSet<>();
        codeSet.addAll(doList.stream().map(WaybillDo::getSenderAreaCode).collect(Collectors.toSet()));
        codeSet.addAll(doList.stream().map(WaybillDo::getCneeAreaCode).collect(Collectors.toSet()));
        codeSet.remove(null);
        Map<Long, String> anMap = areaApiService
                .getFullNameListByCodeSet(codeSet)
                .stream().collect(Collectors.toMap(BinVo::getId, BinVo::getValue));

        // 补充仓库信息和快递公司信息
        voList.forEach(it -> {
            it.setWarehouseName(
                    wnMap.get(it.getWarehouseId())
            );
            it.setExpressCompanyName(
                    enMap.get(it.getExpressCompanyId())
            );
            it.setSenderAreaFullName(
                    anMap.get(it.getSenderAreaCode())
            );
            it.setCneeAreaFullName(
                    anMap.get(it.getCneeAreaCode())
            );
        });

        return voList;
    }

    private LambdaQueryWrapper<WaybillDo> buildPageQueryWrapper(WaybillPageQuery query) {

        LambdaQueryWrapper<WaybillDo> w = Wrappers.lambdaQuery(WaybillDo.class);

        w.eq(query.getMemberId() != null, WaybillDo::getMemberId, query.getMemberId());

        w.eq(query.getOrderId() != null, WaybillDo::getOrderId, query.getOrderId());

        w.eq(query.getTrackingNo() != null, WaybillDo::getTrackingNo, query.getTrackingNo());

        w.ge(query.getWeightFrom() != null, WaybillDo::getWeight, query.getWeightFrom());
        w.le(query.getWeightTo() != null, WaybillDo::getWeight, query.getWeightTo());

        w.eq(query.getWarehouseId() != null, WaybillDo::getWarehouseId, query.getWarehouseId());

        w.eq(query.getExpressCompanyId() != null, WaybillDo::getExpressCompanyId, query.getExpressCompanyId());

        w.eq(query.getState() != null, WaybillDo::getState, query.getState());

        w.like(query.getSenderName() != null, WaybillDo::getSenderName, query.getSenderName());

        w.eq(query.getSenderPhone() != null, WaybillDo::getSenderPhone, query.getSenderPhone());

        w.eq(query.getSenderAreaCode() != null, WaybillDo::getSenderAreaCode, query.getSenderAreaCode());

        w.like(query.getCneeName() != null, WaybillDo::getCneeName, query.getCneeName());

        w.eq(query.getCneePhone() != null, WaybillDo::getCneePhone, query.getCneePhone());

        w.eq(query.getCneeAreaCode() != null, WaybillDo::getCneeAreaCode, query.getCneeAreaCode());

        w.orderByDesc(WaybillDo::getCreateTime);

        return w;
    }
}
